#include "log.h"

bool Log::isViewed;
bool Log::updateStatus;

Log::Log(void)
{
}


Log::~Log(void)
{
  delete [] logStringArray;
  privateSurface->Release();
  d3dxFont->Release();
}


void Log::Release() {
  delete [] logStringArray;
  privateSurface->Release();
  d3dxFont->Release();
}


void Log::InitStatic() {
  Log::isViewed = false;
  Log::updateStatus = false;
}


void Log::Write(LPWSTR inputString, ...) {
  Log::updateStatus = false;
  LPWSTR writingString = (LPWSTR)malloc(sizeof(*writingString)*250);
  LPSYSTEMTIME systemTime = new SYSTEMTIME;
  GetLocalTime(systemTime);
  StringCchPrintf(writingString, 259, L"%2d:%2d:%2d-%2d/%2d/%4d: ",
      systemTime->wHour,
      systemTime->wMinute,
      systemTime->wSecond,
      systemTime->wDay,
      systemTime->wMonth,
      systemTime->wYear);
  delete systemTime;
  StringCchCatW(writingString, 250, inputString);
  // undefined param
  va_list ap;
  va_start(ap, inputString);
  int argCounter = 0;
  while (true) {
    // extra more 2 byte with UNICODE
    LPWSTR strParam  =  va_arg(ap, LPWSTR);
    if (*strParam == NULL) {
      va_start(ap, inputString);
      break;
    }
    argCounter++;
  }
  if (argCounter > 2) {
    argCounter = argCounter - 2;
    for (int i = 0; i < argCounter; i++) {
      LPWSTR strParam  =  va_arg(ap, LPWSTR);
      StringCchCatW(writingString, 250, strParam);
    }
  }
  // add \r\n for input char
  StringCchCatW(writingString, 250, L"\r\n");
  // Open file
  HANDLE handleFile = CreateFileW(
      Path::logFileName,
      FILE_ALL_ACCESS, FILE_SHARE_READ | FILE_SHARE_WRITE,
      NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 
  if (handleFile == NULL) {
    return;
  }
  // Seek to end of file
  SetFilePointer(handleFile, 0, 0, FILE_END);
  //
  LPDWORD byteToWrite = (LPDWORD)malloc(sizeof(*byteToWrite));
  StringCchLength(writingString, 250, (size_t*)byteToWrite);
  // Write file
  LPDWORD byteWritten = (LPDWORD)malloc(sizeof(*byteWritten));
  WriteFile(handleFile, (LPCVOID)writingString,
            (*byteToWrite) * 2, // UTF-8 (2 byte)
            byteWritten, NULL);
  CloseHandle(handleFile);
  // Release memeory
  free(writingString);
  free(byteToWrite);
  free(byteWritten);
}


CString* Log::Read(int &numberRow) {
  Log::updateStatus = true;
  // Open file
  HANDLE handleFile = CreateFileW(
      Path::logFileName, 
      FILE_ALL_ACCESS, FILE_SHARE_READ | FILE_SHARE_WRITE,
      NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if(handleFile == NULL) { // check handle created or not
    numberRow = 0;
    CloseHandle(handleFile);
    // Release memory
    return NULL;
  }
  // Seek to begin of file 
  LARGE_INTEGER fileSize;
  GetFileSizeEx(handleFile, &fileSize);
  if (fileSize.LowPart > 2048) {
    SetFilePointer(handleFile, -2048, 0, FILE_END);
  } else {
    SetFilePointer(handleFile, 0, 0, FILE_BEGIN);
  }
  LPWSTR logString = (LPWSTR)malloc(sizeof(WCHAR)*fileSize.LowPart); 
  LPDWORD numberByteRead = (LPDWORD)malloc(sizeof(*numberByteRead));
  ReadFile(handleFile, logString, fileSize.LowPart, numberByteRead, NULL);
  CString logText; // logText that return to user
  CString dataText; // string object from getted text.
  dataText.SetString(logString);
  dataText.GetBufferSetLength(*numberByteRead / 2); // unicode save with double byte
  int splitLength = 0;
  int curPos = 0;
  //
  // Count split string
  //
  do {
    CString tempString = dataText.Tokenize(L"\r\n",curPos);
    if (tempString != "") {
      splitLength++;
    } else {
      break;
    } 
  } while (true);
  if(splitLength == 0) {
    numberRow = splitLength;
    CloseHandle(handleFile);
    // Release memory
    free(logString);
    free(numberByteRead);
    return NULL;
  }
  CString* stringArray = new CString[splitLength];
  //
  // Split to array
  //
  int counter = 0;
  curPos = 0;
  do {
    stringArray[counter] = dataText.Tokenize(L"\r\n",curPos);
    if (stringArray[counter] != "") {
      counter++;
    } else {
      break;
    } 
  } while (counter < splitLength);
  // check if string array is smaller require row.
  if (splitLength < numberRow) {
    numberRow = splitLength;
    CloseHandle(handleFile);
    // Release memory
    logText.ReleaseBuffer(0);
    dataText.ReleaseBuffer(0);
    free(logString);
    free(numberByteRead);
    return stringArray;
  } else {
    // Coply value
    splitLength = splitLength - 1;
    CString* arrayResult = new CString[numberRow];
    for (int i = numberRow - 1 ; i >= 0; i--) {
      arrayResult[i] = stringArray[splitLength];
      splitLength--;
    }
    CloseHandle(handleFile);
    // Release memory
    free(logString);
    free(numberByteRead);
    logText.ReleaseBuffer(0);
    dataText.ReleaseBuffer(0);
    return arrayResult;
  }
}


void Log::SwitchState() {
  if (Log::isViewed == false) {
    Log::isViewed = true;
  } else {
    Log::isViewed = false;
  }
}


void Log::Init(Screen* screen) {
  // init screen object
  this->screen = screen;
  this->d3ddev = screen->getDevive();
  this->backBuffer = screen->getBackBuffer();
  this->fontSize = LOGFONTSIZE;
  logStringArray = NULL;
  screen->getDevive()->CreateOffscreenPlainSurface(
      Screen::screenWidth,
      Screen::screenHeight, 
      D3DFMT_X8R8G8B8,
      D3DPOOL_DEFAULT,
      &privateSurface,
      NULL);
 HRESULT hresult = D3DXCreateFont(
      screen->getDevive(),
      fontSize,
      0,
      FW_BOLD,
      0,
      FALSE,
      DEFAULT_CHARSET,
      OUT_DEFAULT_PRECIS,
      DEFAULT_QUALITY,
      DEFAULT_PITCH | FF_DONTCARE,
      L"Arial",
      &d3dxFont);
 // Init rect
 rect.left = 0;
 rect.right = Screen::screenWidth;
 rect.top = 0;
 rect.bottom = LOGSCREEN_HEIGHT;
 // Init color
 black = D3DCOLOR_XRGB(0, 0, 0);
 white = D3DCOLOR_XRGB(255, 255, 255);
   
}


void Log::Render() {
  if(Log::isViewed == true) {
    d3ddev->ColorFill(privateSurface, &rect, black);
    d3ddev->StretchRect(privateSurface, NULL, backBuffer,&rect, D3DTEXF_NONE);
    // Init textRect
    textRect.left = 2; // 2 px from left
    textRect.right = rect.right;
    textRect.bottom = rect.bottom;
    textRect.top = textRect.bottom - fontSize;
    if (Log::updateStatus == false) {
      // Number row = 16;
      this->numberRow = 16;
      delete [] logStringArray;
      this->logStringArray = Log::Read(numberRow);  
    }
    if (logStringArray == NULL) {
      return;
    }
    for (int i = numberRow - 1; i >= 0 ; i--) {
      d3dxFont->DrawTextW(NULL, logStringArray[i], -1, &textRect ,NULL, white);
      textRect.bottom = textRect.bottom - fontSize;
      textRect.top = textRect.top - fontSize;
    }
  }
}

void Log::ErrorExit(LPTSTR lpszFunction) 
{ 
    // Retrieve the system error message for the last-error code

    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    // Display the error message and exit the process

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
        (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, 
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"), 
        lpszFunction, dw, lpMsgBuf); 
    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
    ExitProcess(dw); 
}